#include "../../includes/NeonMath/Vector4.hpp"
using namespace Neon;

Vector4 Vector4::one = Vector4(1.0f, 1.0f, 1.0f, 1.0f);
Vector4 Vector4::wUnit = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
Vector4 Vector4::xUnit = Vector4(1.0f, 0.0f, 0.0f, 0.0f);
Vector4 Vector4::yUnit = Vector4(0.0f, 1.0f, 0.0f, 0.0f);
Vector4 Vector4::zero = Vector4(0.0f, 0.0f, 0.0f, 0.0f);
Vector4 Vector4::zUnit = Vector4(0.0f, 0.0f, 1.0f, 0.0f);

Vector4::Vector4()
	: X(0.0f), Y(0.0f), Z(0.0f), W(0.0f)
{ }

Vector4::Vector4(const nfloat x, const nfloat y, const nfloat z, const nfloat w)
	: X(x), Y(y), Z(z), W(w)
{ }

Vector4::Vector4(const Vector4 &vec)
	: X(vec.X), Y(vec.Y), Z(vec.Z), W(vec.W)
{ }

Vector4&
Vector4::operator =(const Vector4 &rhs)
{
	X = rhs.X;
	Y = rhs.Y;
	Z = rhs.Z;
	W = rhs.W;

	return *this;
}

Vector4
Vector4::operator +() const
{
	return Vector4(+X, +Y, +Z, +W);
}

Vector4
Vector4::operator +(const Vector4 &rhs) const
{
	return Vector4(X + rhs.X, Y + rhs.Y, Z + rhs.Z, W + rhs.W);
}

Vector4
Vector4::operator +(nfloat rhs) const
{
	return Vector4(X + rhs, Y + rhs, Z + rhs, W + rhs);
}

Vector4&
Vector4::operator +=(const Vector4 &rhs)
{
	(*this) = (*this) + rhs;

	return *this;
}

Vector4&
Vector4::operator +=(nfloat rhs)
{
	(*this) = (*this) + rhs;

	return *this;
}

Vector4
Vector4::operator -() const
{
	return Vector4(-X, -Y, -Z, -W);
}

Vector4
Vector4::operator -(const Vector4 &rhs) const
{
	return Vector4(X - rhs.X, Y - rhs.Y, Z - rhs.Z, W - rhs.W);
}

Vector4
Vector4::operator -(nfloat rhs) const
{
	return Vector4(X - rhs, Y - rhs, Z - rhs, W - rhs);
}

Vector4&
Vector4::operator -=(const Vector4 &rhs)
{
	(*this) = (*this) - rhs;

	return *this;
}

Vector4&
Vector4::operator -=(nfloat rhs)
{
	(*this) = (*this) - rhs;

	return *this;
}

Vector4
Vector4::operator *(const Vector4 &rhs) const
{
	return Vector4(X * rhs.X, Y * rhs.Y, Z * rhs.Z, W * rhs.W);
}

Vector4
Vector4::operator *(nfloat rhs) const
{
	return Vector4(X * rhs, Y * rhs, Z * rhs, W * rhs);
}

Vector4&
Vector4::operator *=(const Vector4 &rhs)
{
	(*this) = (*this) * rhs;

	return *this;
}

Vector4&
Vector4::operator *=(nfloat rhs)
{
	(*this) = (*this) * rhs;

	return *this;
}

Vector4
Vector4::operator /(const Vector4 &rhs) const
{
	return Vector4(X / rhs.X, Y / rhs.Y, Z / rhs.Z, W / rhs.W);
}

Vector4
Vector4::operator /(nfloat rhs) const
{
	rhs = 1.0f / rhs;
	return Vector4(X * rhs, Y * rhs, Z * rhs, W * rhs);
}

Vector4&
Vector4::operator /=(const Vector4 &rhs)
{
	(*this) = (*this) / rhs;

	return *this;
}

Vector4&
Vector4::operator /=(nfloat rhs)
{
	(*this) = (*this) / rhs;

	return *this;
}

bool
Vector4::operator ==(const Vector4 &rhs) const
{
	if(X == rhs.X && Y == rhs.Y && Z == rhs.Z && W == rhs.W) {
		return true;
	}

	return false;
}

bool
Vector4::operator !=(const Vector4 &rhs) const
{
	if(X != rhs.X || Y != rhs.Y || Z != rhs.Z || W != rhs.W) {
		return true;
	}

	return false;
}

Vector4
Vector4::barycentric(const Vector4 &vec1, const Vector4 &vec2, const Vector4 &vec3, const nfloat amount1, const nfloat amount2)
{
	return (vec1 + (amount1 * (vec2 - vec1)) + (amount2 * (vec3 - vec1)));
}

Vector4
Vector4::catmullRom(const Vector4 &vec1, const Vector4 &vec2, const Vector4 &vec3, const Vector4 &vec4, const nfloat amount)
{
	nfloat num = amount * amount;
	nfloat num2 = amount * num;

	return (0.5f * ((((2.0f * vec2) + ((-vec1 + vec3) * amount)) + (((((2.0f * vec1) - (5.0f * vec2)) + (4.0f * vec3)) - vec4) * num)) + ((((-vec1 + (3.0f * vec2)) - (3.0f * vec3)) + vec4) * num2)));
}

Vector4
Vector4::clamp(const Vector4 &vec, const Vector4 &min, const Vector4 &max)
{
	nfloat x = vec.X;

	x = (x > max.X) ? max.X : x;
	x = (x < min.X) ? min.X : x;

	nfloat y = vec.Y;

	y = (x > max.Y) ? max.Y : y;
	y = (x < min.Y) ? min.Y : y;

	nfloat z = vec.Z;

	z = (z > max.Z) ? max.Z : z;
	z = (z < min.Z) ? min.Z : z;

	nfloat w = vec.W;

	w = (w > max.W) ? max.W : w;
	w = (w < min.W) ? min.W : w;

	return Vector4(x, y, z, w);
}

nfloat
Vector4::distance(const Vector4 &vec1, const Vector4 &vec2)
{
	return (vec1 - vec2).length();
}

nfloat
Vector4::distance(const Vector4 &vec) const
{
	return (*this - vec).length();
}

nfloat
Vector4::distanceSquared(const Vector4 &vec1, const Vector4 &vec2)
{
	return (vec1 - vec2).lengthSquared();
}

nfloat
Vector4::distanceSquared(const Vector4 &vec) const
{
	return (*this - vec).lengthSquared();
}

nfloat
Vector4::dotProduct(const Vector4 &vec1, const Vector4 &vec2)
{
	return vec1.X * vec2.X + vec1.Y * vec2.Y + vec1.Z * vec2.Z + vec1.W * vec2.W;
}

nfloat
Vector4::dotProduct(const Vector4 &vec) const
{
	return X * vec.X + Y * vec.Y + Z * vec.Z + W * vec.W;
}

Vector4
Vector4::hermite(const Vector4 &vec1, const Vector4 &tang1, const Vector4 &vec2, const Vector4 &tang2, const nfloat amount)
{
	nfloat num = amount * amount;
	nfloat num2 = amount * num;
	nfloat num3 = ((2.0f * num2) - (3.0f * num)) + 1.0f;
	nfloat num4 = (-2.0f * num2) + (2.0f * num);
	nfloat num5 = (num2 - (2.0f * num)) + amount;
	nfloat num6 = num2 - num;

	return ((((vec1 * num3) + (vec2 * num4)) + (tang1 * num5)) + (tang2 * num6));
}

bool
Vector4::isPerpendicular(const Vector4 &vec1, const Vector4 &vec2)
{
	return (dotProduct(vec1, vec2) == 0);
}

bool
Vector4::isPerpendicular(const Vector4 &vec) const
{
	return (dotProduct(vec) == 0);
}

nfloat
Vector4::length(const Vector4 &vec)
{
	return sqrt(dotProduct(vec, vec));
}

nfloat
Vector4::length() const
{
	return sqrt(dotProduct(*this));
}

nfloat
Vector4::lengthSquared(const Vector4 &vec)
{
	return dotProduct(vec, vec);
}

nfloat
Vector4::lengthSquared() const
{
	return dotProduct(*this);
}

Vector4
Vector4::lerp(const Vector4 &vec1, const Vector4 &vec2, const nfloat amount)
{
	return (vec1 + ((vec2 - vec1 * amount)));
}

Vector4
Vector4::max(const Vector4 &vec1, const Vector4 &vec2)
{
	nfloat x = qMax(vec1.X, vec2.X);
	nfloat y = qMax(vec1.Y, vec2.Y);
	nfloat z = qMax(vec1.Z, vec2.Z);
	nfloat w = qMax(vec1.W, vec2.W);

	return Vector4(x, y, z, w);
}

Vector4
Vector4::min(const Vector4 &vec1, const Vector4 &vec2)
{
	nfloat x = qMin(vec1.X, vec2.X);
	nfloat y = qMin(vec1.Y, vec2.Y);
	nfloat z = qMin(vec1.Z, vec2.Z);
	nfloat w = qMin(vec1.W, vec2.W);

	return Vector4(x, y, z, w);
}

Vector4
Vector4::normalise(const Vector4 &vec)
{
	nfloat len = length(vec);

	if(len == 0.0f) {
		len = 1.0f;
	}

	return vec / len;
}

void
Vector4::normalise()
{
	nfloat len = length();

	if(len == 0.0f) {
		len = 1.0f;
	}

	(*this) /= len;
}

Vector4
Vector4::reflect(const Vector4 &vec, const Vector4 &normal)
{
	nfloat num = vec.dotProduct(normal);

	return (vec - ((2.0f * num) * normal));
}

Vector4
Vector4::smoothStep(const Vector4 &vec1, const Vector4 &vec2, nfloat amount)
{
	amount = (amount > 1.0f) ? 1.0f : ((amount < 0.0f) ? 0.0f : amount);
	amount = (amount * amount) * (3.0f - (2.0f * amount));

	return (vec1 + ((vec2 - vec1) * amount));
}